;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Copyright 2021 fanguangping
; 
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
; 
;     http://www.apache.org/licenses/LICENSE-2.0
; 
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

#lang racket

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Hash class
; - HashTable: entry value not replaced
; - HashMap: entry value replaced when put
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(require "../common/constants.scm")
(require "../common/messages.scm")
(require "../common/utils.scm")
(require "Object.scm")

(provide (all-defined-out))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Make Hash class
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (MAKE-Hash self table replace-mode)
  (define (hash-put-all! t1 t2)
    (map (lambda (kv) (ask t1 +M-Hash-put+ (car kv) (cadr kv))) (ask t2 +M-Hash-elements+)))
  (define (value-to-string v)
    (if replace-mode
        (object-to-string (car v))
        (string-join (map object-to-string v) ", ")))
  (lambda (message)
    (cond
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Get value from a hashtable or hashmap
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-get+)
       (lambda (key) (hash-ref table key)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Put value to a hashtable or hashmap
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-put+)
       (lambda (key value)
         (if replace-mode
             (hash-set! table key (cons value +nil+))
             (let ((v (if (hash-has-key? table key) (hash-ref table key) +nil+)))
               (if (member value v) v
                   (hash-set! table key (cons value v)))))))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Determine whether hashtable or hashmap contains key
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-contains-key+)
       (lambda (key) (hash-has-key? table key)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Hashtable or hashmap elements
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-elements+)
       (lambda () (hash->list table)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Hashtable or hashmap merge
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-merge+)
       (lambda (hash-list)
         (map
          (lambda (t)
            (hash-put-all! self t))
          hash-list)
         self))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Hashtable or hashmap clear
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-clear+)
       (lambda ()
         (hash-clear! table)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Hashtable or hashmap copy
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-copy+)
       (lambda ()
         (create-instance MAKE-Hash (hash-copy table) replace-mode)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; The table where data hashed
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Hash-table+)
       (lambda ()
         table))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; Hashmap equal
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-HashMap-equal+)
       (lambda (ht)
         (let ((table2 (ask ht +M-Hash-table+)))
           (cond
             ((not (= (hash-count table) (hash-count table2))) +false+)
             ((not (andmap (lambda (t) (object-equals? (car (hash-ref table t)) (car (hash-ref table2 t)))) (hash-keys table))) +false+)
             (else +true+)))))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; to string
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Object-to-string+)
       (lambda ()
         (string-join (hash-map table (lambda (k v) (string-append (object-to-string k) " -> " (value-to-string v)))) ", ")))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ; type
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ((= message +M-Object-type+)
       (lambda () 'Hash))

      (else +nil+))))

(define (CREATE-HashTable)
  (create-instance MAKE-Hash (make-hash) +false+))

(define (CREATE-HashMap)
  (create-instance MAKE-Hash (make-hash) +true+))

(define (CREATE-HashMap-By-List kvlist)
  (let ((hm CREATE-HashMap))
    (map (lambda (t) (hashmap-put! hm (car t) (cdr t))) kvlist)
    hm))

(define (hashtable-put! ht key value)
  (ask ht +M-Hash-put+ key value))

(define (hashtable-get ht key)
  (ask ht +M-Hash-get+ key))

(define (hashtable-contains-key? ht key)
  (ask ht +M-Hash-contains-key+ key))

(define (hashtable-elements ht)
  (ask ht +M-Hash-elements+))

(define (hashtable-merge! ht table-list)
  (ask ht +M-Hash-merge+ table-list))

(define (hashtable-clear! ht)
  (ask ht +M-Hash-clear+))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Description: usage of hashmap
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (hashmap-grow! hm key value)
  (let ((m (if (null? hm) (CREATE-HashMap) hm)))
    (hashmap-put! m key value)
    m))

(define (hashmap-put! hm key value)
  (ask hm +M-Hash-put+ key value))

(define (hashmap-get hm key)
  (car (ask hm +M-Hash-get+ key)))

(define (hashmap-contains-key? hm key)
  (ask hm +M-Hash-contains-key+ key))

(define (hashmap-elements hm)
  (ask hm +M-Hash-elements+))

(define (hashmap-merge! hm table-list)
  (ask hm +M-Hash-merge+ table-list))

(define (hashmap-clear! hm)
  (ask hm +M-Hash-clear+))

(define (hashmap-equal? hm1 hm2)
  (ask hm1 +M-HashMap-equal+ hm2))

(define (hashmap-copy hm)
  (ask hm +M-Hash-copy+))
